Skip to content

fix(onboarding): retry job title eligibility check after a failed request - #1412

Closed
gabrielseco wants to merge 2 commits into
found-1869-hook-up-the-form-onblur-events-to-trigger-the-jobfrom
fix/job-title-eligibility-retry-after-failure
Closed

gabrielseco wants to merge 2 commits into
found-1869-hook-up-the-form-onblur-events-to-trigger-the-jobfrom
fix/job-title-eligibility-retry-after-failure

Conversation

@gabrielseco

@gabrielseco gabrielseco commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

If the job title eligibility check fails once (e.g. a network or server error), it now runs again the next time the user leaves a field. Before, it was never retried unless the user changed their role answers.

Why

Cursor Bugbot flagged this on #1406 ("Failed checks skip later retries"). A single transient failure left the employment without a recorded check, and the user couldn't recover it without editing a role field.

What changed

Toggle details
  • useJobTitleEligibilityCheck.check() no longer gates the request on "params changed". The query cache already handles dedup: jobTitleEligibilityCheckOptions uses staleTime: Infinity, so a successful result is returned from cache with no request, while an errored (no data) query is stale and refetches. In-flight requests are deduped by query.fetch.
  • setParams still only runs when params change, so the useQuery key is stable.
  • No test changes here. The retry-after-failure test lives in test(onboarding): cover the job title eligibility blur dedup #1408, which also carries this same fix so its CI passes on its own.
  • Targets feat(onboarding): wire onBlur events to job title eligibility check #1406's branch, not main. No public API change.

Screenshots

N/A

Related Resources

Testing

  • Tested against the example/ app in a browser
  • Feature flag: job_title_eligibility

🤖 Generated with Claude Code

…uest

check() recorded the new params before the request finished and only
fired the request when params changed. With retry: false and
staleTime: Infinity, a failed first attempt was never retried on later
blurs unless a role field was edited.

Always call queryClient.query when params are complete: a successful
result is served from cache (no request), an errored/missing result
refetches, and in-flight requests are deduped by the query cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

Metric Current Previous Change Status
Total (gzip) 168.07 kB 168.08 kB -5 B (-0.0%) 🟢
Total (raw) 561.72 kB 561.73 kB -8 B (-0.0%) 🟢
CSS (gzip) 21.94 kB 21.94 kB 0 B (0%) 🟢
CSS (raw) 114.43 kB 114.43 kB 0 B (0%) 🟢

Size Limits

  • ✅ Total gzipped: 168.07 kB / 350 kB (48.0%)
  • ✅ Total raw: 561.72 kB / 850 kB (66.1%)
  • ✅ CSS gzipped: 21.94 kB / 25 kB (87.8%)

Largest Files (Top 5)

  1. CheckBoxField-DBWQDi6N.js - 24.64 kB (0 B (0%))
  2. ContractorOnboarding-Bb6M25-p.js - 13.55 kB (0 B (0%))
  3. Onboarding-CY1vt1jP.js - 11.68 kB (new)
  4. Termination-D8_Yz_Ju.js - 11.07 kB (0 B (0%))
  5. styles.css - 10.97 kB (0 B (0%))
View All Files (75 total)
File Size (gzip) Change
CheckBoxField-DBWQDi6N.js 24.64 kB 0 B (0%)
ContractorOnboarding-Bb6M25-p.js 13.55 kB 0 B (0%)
Onboarding-CY1vt1jP.js 11.68 kB new
Termination-D8_Yz_Ju.js 11.07 kB 0 B (0%)
styles.css 10.97 kB 0 B (0%)
index.css 10.97 kB 0 B (0%)
CostCalculator-B24h9gs5.js 10.65 kB 0 B (0%)
internals-C1kFEhms.js 8.23 kB 0 B (0%)
index.js 5.05 kB +3 B (+0.1%)
JSONSchemaForm-D8Xz5yJL.js 4.9 kB 0 B (0%)

✅ Bundle size check passed

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Deploy preview for remote-flows ready!

Project:remote-flows
Status: ✅  Deploy successful!
Preview URL:https://remote-flows-f5qppz65c-remotecom.vercel.app
Latest Commit:cc4cf81

Deployed with vercel-action

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Deploy preview for adp-cost-calculator ready!

Project:adp-cost-calculator
Status: ✅  Deploy successful!
Preview URL:https://adp-cost-calculator-kc0ygq6hf-remotecom.vercel.app
Latest Commit:cc4cf81

Deployed with vercel-action

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📊 Coverage Report

✅ Coverage increased! 🎉

Metric Current Previous Change Status
Lines 85.98% 85.97% +0.02% 🟢
Statements 85.56% 85.55% +0.01% 🟢
Functions 84.49% 84.49% 0% ⚪
Branches 77.11% 77.07% +0.04% 🟢

Detailed Breakdown

Lines Coverage
  • Covered: 4877 / 5672
  • Coverage: 85.98%
  • Change: +0.02% (0 lines)
Statements Coverage
  • Covered: 4961 / 5798
  • Coverage: 85.56%
  • Change: +0.01% (0 statements)
Functions Coverage
  • Covered: 1296 / 1534
  • Coverage: 84.49%
  • Change: 0% (0 functions)
Branches Coverage
  • Covered: 3009 / 3902
  • Coverage: 77.11%
  • Change: +0.04% (0 branches)

✅ Coverage check passed

gabrielseco added a commit that referenced this pull request Sep 28, 2026
…r a failure

A failed check must be retried on the next blur even when the role
answers are unchanged. Depends on the fix in #1412; fails until it
lands on the base branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gabrielseco added a commit that referenced this pull request Sep 28, 2026
…uest

Same change as #1412, so this PR's retry test passes on its own. The hunk
is identical, so merging both is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gabrielseco added a commit that referenced this pull request Oct 8, 2026
* test(onboarding): cover the job title eligibility blur dedup

Adds two integration tests against the rendered OnboardingFlow (not the
headless hook) since that's the surface most consumers actually hit:
blurring the role fields once they're complete fires the check, and a
later blur with unchanged values doesn't fire it again.

Also adds unit tests for the pure getJobTitleEligibilityParams (slug
field gate, incomplete/invalid fields, a hidden param field still
allowing the others through, job_title inclusion).

Adds a minimal, purpose-built contract_details fixture (following the
v1-italy-apl.ts "trimmed down" convention) with just the four fields the
check cares about, rather than driving the full multi-hundred-field
country schemas through this scenario.

The real dedup bug this uncovered (paramsChanged never actually true) is
fixed upstream in the parent branch, not in this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(onboarding): give the dedup test time for a broken fetch to land

Bugbot flagged that the second test's final assertion ran right after
user.tab() resolves, but checkJobTitleEligibility is fire-and-forget from
OnboardingForm's onBlur - it awaits handleValidation, parseFormValues, and
the request itself, none of which user.tab() waits on. A regression that
fires a second request wouldn't have reached the spy by assertion time,
so the test would have passed even with a broken dedup.

Verified by temporarily disabling both dedup layers (forcing paramsChanged
true and setting staleTime: 0): without this fix the test still passed;
with it, it correctly fails and catches the regression within the 200ms
window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(onboarding): explain the inverted waitFor in the dedup test

The waitFor + .catch pattern for asserting an async side effect does not
eventually happen is unusual enough to warrant a why-comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(onboarding): reword the waitFor comment in plain language

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(onboarding): cover retrying the job title eligibility check after a failure

A failed check must be retried on the next blur even when the role
answers are unchanged. Depends on the fix in #1412; fails until it
lands on the base branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(onboarding): retry job title eligibility check after a failed request

Same change as #1412, so this PR's retry test passes on its own. The hunk
is identical, so merging both is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(example): rename vite config to .mts to silence native config loader warning (#1394)

The example package is "type": "commonjs" (dev_server.js and api/ use
require), so Vite flagged ESM syntax in vite.config.ts as unsupported by
the upcoming native config loader. The .mts extension marks just the
config as ESM without flipping the whole package.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(scripts): send checkbox const value, not [true], in seed-onboarding (#1389)

fakeValueFor() hardcoded [true] for checkbox fields, mimicking a
jsonType-array shape that doesn't match how the real form submits
const-based checkboxes (e.g. FRA's ack_non_eligible_job_titles, a
jsonType string with const: "acknowledged"). parseFormValuesToAPI
swaps the RHF boolean for field.const at submit time; mirror that
here instead of sending the raw fill value, or the API 422s with a
type mismatch.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(scripts): seed onboarding directly against a gateway env (#1371)

* feat(scripts): seed onboarding directly against a gateway env

Lets seed-onboarding.mjs bypass the local example dev server via --env=<name>,
reusing example/api/{utils,get_token,proxy}.js for auth against .env.<env> at
the repo root. Prints a ready-to-click app link (?employmentId= prefilled,
via VITE_APP_URL) when set. Onboarding.tsx now reads employmentId from the
URL query string so that link actually prefills the intro form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skills): add verify-sandbox-deployed-app skill

Captures the manual sandbox-verification loop (seed via --env=sandbox, log
past the Vercel password gate with VITE_APP_PASSWORD, drive the flow, check
the claim) as a repeatable skill instead of re-deriving it each time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* style: fix oxfmt formatting in verify-sandbox-deployed-app skill

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skills): remove verify-sandbox-deployed-app skill

Split into its own PR (#1379) so it can be reviewed independently of
the seed-onboarding env flag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(e2e): give sandbox onboarding specs room for slow backend writes

The basic and Germany onboarding specs create real employments against
the sandbox and ran against Playwright's 30s test / 5s expect defaults.
Traces from PR #1371 and main show nothing hanging: Germany reached the
final ILA sign click at 27.2s, leaving <3s for the sign POST, and Spain
failed step-title assertions while ~3s employment writes were in flight.

- test.slow() on both onboarding specs (90s budget)
- expectOnboardingStep helper with a 15s timeout for step transitions,
  which are gated on sandbox writes rather than rendering
- wait on the ILA sign POST and assert it succeeded before checking the
  dialog closed, so a backend rejection reports as such

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(e2e): gate onboarding steps on save responses, set timeouts globally

Replaces the per-spec test.slow() and the 15s step-title timeout with:

- submitAndWaitForSave / clickAndWaitForSave helpers that wait for the
  request each step's submit fires (POST /v1/employments, POST
  engagement-agreement-details, PATCH /v1/employments/{id}, PUT
  benefit-offers, POST .../sign) and assert it succeeded, instead of
  waiting for the "Loading..." text to disappear. A backend failure now
  reports method, path and status.
- Global timeout (60s) and expect timeout (10s) in playwright.config.ts,
  matching Dragon's defaults. Response waits make failures precise but do
  not make the sandbox faster; the Germany flow alone takes ~30s on a
  good day, so the 30s default budget has to go up regardless.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* 1.57.0 (#1407)

* chore: prepare release v1.57.0

* docs: group 1.57.0 changelog by type

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* update changelog

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* docs: surface the inline-comment convention in CLAUDE.md (#1405)

* docs: surface the inline-comment convention in CLAUDE.md

The comment-density rule (zero by default, one line max for a
genuinely non-obvious why) only lived in the untracked
CLAUDE.local.md and in .cursor/BUGBOT.md, which Cursor's Bugbot reads
for its own reviews but Claude Code doesn't load as project
instructions. Add a pointer in CLAUDE.md's Conventions section so it's
visible up front instead of surfacing after the fact via a bot
comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: describe what a good inline comment looks like

Extends the opt-in comment rule with what "good" actually means when a
comment does get written (the rare non-obvious-why case, or on explicit
request): plain English a newcomer can follow, not jargon-dense
shorthand - even if that takes a couple of sentences.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: exempt human-requested comments from the one-line cap

The one-line cap exists to stop unprompted, jargon-dense comments -
it's not meant to force a human-requested explanation into an
unreadable single line. Carves out that exception in both CLAUDE.md
and .cursor/BUGBOT.md: a comment explicitly asked for can take as many
plain sentences as it needs; one added on the AI's own initiative for
the non-obvious-why case stays capped at one line, and shouldn't exist
at all if the code it's next to is actually simple.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* docs: document import order convention (#1404)

React first, then external packages, then @remoteoss/* packages, then
absolute (@/) imports, then relative imports. Not enforced by oxlint or
oxfmt today (verified: import/order is not a supported oxlint rule and
oxfmt does not reorder imports) — this documents the existing manual
convention so it's explicit for humans and Claude Code.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore(deps): update dependency @remoteoss/remote-json-schema-form-kit to v1.1.2 (#1409)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(onboarding): block invite while employment is in job_title_review (#1403)

Dragon hard-gates its Invite button on employment.status !==
'job_title_review' (SendInvitation.jsx); we didn't. Tiger already
rejects the invite call server-side while in this status (409 'Can't
invite employee in status job_title_review' on the partner API, 422
on the internal one), so this closes the client-side gap rather than
introducing new blocking behavior.

job_title_review is independent of the (still unmerged) job title
eligibility check feature flag: it's part of the generated
Employment['status'] type already, and Tiger can put an employment
into this status any time its stage-2 AI check needs human review, so
the SDK should respect it regardless of whether a partner has opted
into that feature.

Addresses checklist item 1 in JOB_TITLE_ELIGIBILITY_CHECKLIST.md (on
feat/onboarding-job-title-eligibility-check).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore: ignore CLAUDE.local.md (#1400)

* chore: ignore CLAUDE.local.md

Adds an explicit .gitignore entry for CLAUDE.local.md, a local-only
companion to CLAUDE.md for per-developer notes that shouldn't be
checked in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: copy CLAUDE.local.md into new worktrees

CLAUDE.local.md is gitignored, so a plain `git worktree add` doesn't
carry it over and Claude Code loses that context in worktree sessions.
Copy it the same way example/.env is already seeded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(scripts): copy root .env.<env> files into new worktrees

npm run seed:onboarding -- --env=<name> reads .env.<name> from the repo
root, but create-worktree.ts only ever copied example/.env - a fresh
worktree silently lost access to gateway-targeting credentials like
.env.sandbox even though it's a root-level, not example/-level, file.

Mirrors the CLAUDE.local.md copy step just above it: glob root for any
.env.* file and copy each one into the new worktree. Also gitignores
.env.* (previously only .env itself was ignored), since these files
carry real credentials and weren't excluded from `git add -A`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore(deps-dev): update dependency dotenv to v18.0.1 (#1410)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps-dev): update dependency tsx to v4.23.15 (#1411)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
gabrielseco added a commit that referenced this pull request Oct 8, 2026
…1406)

* feat(onboarding): add job_title_eligibility feature flag

Splits the flag declaration out of #1395 so it can land on its own:
adds 'job_title_eligibility' to the OnboardingFeatures union (with its
JSDoc) and enables it in the example app. No behavior wired up yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(onboarding): wire onBlur events to job title eligibility check

Adds the useJobTitleEligibilityCheck hook and jobTitleEligibilityCheckOptions
query, and wires OnboardingForm's onBlur to call the flowBag's new
checkJobTitleEligibility(values). The check only runs on the
contract_details step, when 'job_title_eligibility' is enabled, and when
the role fields (role_description, role_is_onsite, role_requires_license)
are filled and valid.

Dedup: params are compared against the previous check (fast-deep-equal)
before firing a request, and jobTitleEligibilityCheckOptions sets
staleTime: Infinity since the query key already encodes employmentId +
params. Without both, react-query's built-in dedup only covers truly
concurrent calls with an identical key — an imperative queryClient.query()
call is not a mounted observer, so it treats every call as a fresh mount
and refetches under the default staleTime: 0.

The result isn't consumed yet (no UI reacts to eligibility yet) - follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(onboarding): drop debug logs and fix job title eligibility dedup

Removes the console.log statements left over from development.

Also fixes a real bug surfaced while writing tests for this: paramsChanged
was read immediately after calling setParams(updaterFn), assuming the
functional updater runs synchronously. It doesn't reliably do that, so
paramsChanged came back false almost every time - meaning the eligibility
check never actually fired a request, for any input. Fixed by comparing
against the params value already in the closure before calling setParams,
instead of depending on a side effect inside the updater.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: remove accidentally committed local settings file

.claude/settings.json is a personal/local permission-allowlist file, not
meant for this PR - it got swept into the previous commit because it was
already staged from earlier local work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(onboarding): match checkJobTitleEligibility's docs to its behavior

The JSDoc claimed the check also runs on entering the contract details
step and before submitting it, but nothing calls it that way - only
OnboardingForm's onBlur does. Since useOnboarding is reachable through
the public ./flows/* entry point, this overstated the contract for
anyone building a custom UI around the headless hook.

Reworded to describe only what's actually wired up, and pointed custom
UIs at calling checkJobTitleEligibility themselves for other trigger
points instead of implying it happens automatically.

Also drops stepValues, initialContractDetailsValues, and fieldValues
from useJobTitleEligibilityCheck's params - they were threaded through
from hooks.tsx but never used inside the hook, leftover scaffolding for
the same unimplemented triggers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(onboarding): close race between overlapping eligibility checks

check() compared nextParams against params captured from the render
closure after two awaits (handleValidation, parseFormValues). If a second
blur's check() started before the first one's setParams had committed a
re-render, both calls would compare against the same stale params -
depending on timing, that can either fire a duplicate request or wrongly
treat a later complete result as unchanged and skip it. Flagged by
Bugbot on PR #1406, independently of the same race I'd already called
out in review.

Fixed by tracking params in a ref alongside the state (state still drives
the reactive useQuery's enabled/key). Ref writes are synchronous and
immediately visible to any concurrently-resolving check() call, so the
comparison always sees the latest known params regardless of render
timing, closing the window entirely rather than narrowing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* revert

* test(onboarding): cover the job title eligibility blur dedup (#1408)

* test(onboarding): cover the job title eligibility blur dedup

Adds two integration tests against the rendered OnboardingFlow (not the
headless hook) since that's the surface most consumers actually hit:
blurring the role fields once they're complete fires the check, and a
later blur with unchanged values doesn't fire it again.

Also adds unit tests for the pure getJobTitleEligibilityParams (slug
field gate, incomplete/invalid fields, a hidden param field still
allowing the others through, job_title inclusion).

Adds a minimal, purpose-built contract_details fixture (following the
v1-italy-apl.ts "trimmed down" convention) with just the four fields the
check cares about, rather than driving the full multi-hundred-field
country schemas through this scenario.

The real dedup bug this uncovered (paramsChanged never actually true) is
fixed upstream in the parent branch, not in this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(onboarding): give the dedup test time for a broken fetch to land

Bugbot flagged that the second test's final assertion ran right after
user.tab() resolves, but checkJobTitleEligibility is fire-and-forget from
OnboardingForm's onBlur - it awaits handleValidation, parseFormValues, and
the request itself, none of which user.tab() waits on. A regression that
fires a second request wouldn't have reached the spy by assertion time,
so the test would have passed even with a broken dedup.

Verified by temporarily disabling both dedup layers (forcing paramsChanged
true and setting staleTime: 0): without this fix the test still passed;
with it, it correctly fails and catches the regression within the 200ms
window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(onboarding): explain the inverted waitFor in the dedup test

The waitFor + .catch pattern for asserting an async side effect does not
eventually happen is unusual enough to warrant a why-comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(onboarding): reword the waitFor comment in plain language

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(onboarding): cover retrying the job title eligibility check after a failure

A failed check must be retried on the next blur even when the role
answers are unchanged. Depends on the fix in #1412; fails until it
lands on the base branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(onboarding): retry job title eligibility check after a failed request

Same change as #1412, so this PR's retry test passes on its own. The hunk
is identical, so merging both is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(example): rename vite config to .mts to silence native config loader warning (#1394)

The example package is "type": "commonjs" (dev_server.js and api/ use
require), so Vite flagged ESM syntax in vite.config.ts as unsupported by
the upcoming native config loader. The .mts extension marks just the
config as ESM without flipping the whole package.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(scripts): send checkbox const value, not [true], in seed-onboarding (#1389)

fakeValueFor() hardcoded [true] for checkbox fields, mimicking a
jsonType-array shape that doesn't match how the real form submits
const-based checkboxes (e.g. FRA's ack_non_eligible_job_titles, a
jsonType string with const: "acknowledged"). parseFormValuesToAPI
swaps the RHF boolean for field.const at submit time; mirror that
here instead of sending the raw fill value, or the API 422s with a
type mismatch.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(scripts): seed onboarding directly against a gateway env (#1371)

* feat(scripts): seed onboarding directly against a gateway env

Lets seed-onboarding.mjs bypass the local example dev server via --env=<name>,
reusing example/api/{utils,get_token,proxy}.js for auth against .env.<env> at
the repo root. Prints a ready-to-click app link (?employmentId= prefilled,
via VITE_APP_URL) when set. Onboarding.tsx now reads employmentId from the
URL query string so that link actually prefills the intro form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skills): add verify-sandbox-deployed-app skill

Captures the manual sandbox-verification loop (seed via --env=sandbox, log
past the Vercel password gate with VITE_APP_PASSWORD, drive the flow, check
the claim) as a repeatable skill instead of re-deriving it each time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* style: fix oxfmt formatting in verify-sandbox-deployed-app skill

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skills): remove verify-sandbox-deployed-app skill

Split into its own PR (#1379) so it can be reviewed independently of
the seed-onboarding env flag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(e2e): give sandbox onboarding specs room for slow backend writes

The basic and Germany onboarding specs create real employments against
the sandbox and ran against Playwright's 30s test / 5s expect defaults.
Traces from PR #1371 and main show nothing hanging: Germany reached the
final ILA sign click at 27.2s, leaving <3s for the sign POST, and Spain
failed step-title assertions while ~3s employment writes were in flight.

- test.slow() on both onboarding specs (90s budget)
- expectOnboardingStep helper with a 15s timeout for step transitions,
  which are gated on sandbox writes rather than rendering
- wait on the ILA sign POST and assert it succeeded before checking the
  dialog closed, so a backend rejection reports as such

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(e2e): gate onboarding steps on save responses, set timeouts globally

Replaces the per-spec test.slow() and the 15s step-title timeout with:

- submitAndWaitForSave / clickAndWaitForSave helpers that wait for the
  request each step's submit fires (POST /v1/employments, POST
  engagement-agreement-details, PATCH /v1/employments/{id}, PUT
  benefit-offers, POST .../sign) and assert it succeeded, instead of
  waiting for the "Loading..." text to disappear. A backend failure now
  reports method, path and status.
- Global timeout (60s) and expect timeout (10s) in playwright.config.ts,
  matching Dragon's defaults. Response waits make failures precise but do
  not make the sandbox faster; the Germany flow alone takes ~30s on a
  good day, so the 30s default budget has to go up regardless.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* 1.57.0 (#1407)

* chore: prepare release v1.57.0

* docs: group 1.57.0 changelog by type

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* update changelog

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* docs: surface the inline-comment convention in CLAUDE.md (#1405)

* docs: surface the inline-comment convention in CLAUDE.md

The comment-density rule (zero by default, one line max for a
genuinely non-obvious why) only lived in the untracked
CLAUDE.local.md and in .cursor/BUGBOT.md, which Cursor's Bugbot reads
for its own reviews but Claude Code doesn't load as project
instructions. Add a pointer in CLAUDE.md's Conventions section so it's
visible up front instead of surfacing after the fact via a bot
comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: describe what a good inline comment looks like

Extends the opt-in comment rule with what "good" actually means when a
comment does get written (the rare non-obvious-why case, or on explicit
request): plain English a newcomer can follow, not jargon-dense
shorthand - even if that takes a couple of sentences.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: exempt human-requested comments from the one-line cap

The one-line cap exists to stop unprompted, jargon-dense comments -
it's not meant to force a human-requested explanation into an
unreadable single line. Carves out that exception in both CLAUDE.md
and .cursor/BUGBOT.md: a comment explicitly asked for can take as many
plain sentences as it needs; one added on the AI's own initiative for
the non-obvious-why case stays capped at one line, and shouldn't exist
at all if the code it's next to is actually simple.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* docs: document import order convention (#1404)

React first, then external packages, then @remoteoss/* packages, then
absolute (@/) imports, then relative imports. Not enforced by oxlint or
oxfmt today (verified: import/order is not a supported oxlint rule and
oxfmt does not reorder imports) — this documents the existing manual
convention so it's explicit for humans and Claude Code.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore(deps): update dependency @remoteoss/remote-json-schema-form-kit to v1.1.2 (#1409)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(onboarding): block invite while employment is in job_title_review (#1403)

Dragon hard-gates its Invite button on employment.status !==
'job_title_review' (SendInvitation.jsx); we didn't. Tiger already
rejects the invite call server-side while in this status (409 'Can't
invite employee in status job_title_review' on the partner API, 422
on the internal one), so this closes the client-side gap rather than
introducing new blocking behavior.

job_title_review is independent of the (still unmerged) job title
eligibility check feature flag: it's part of the generated
Employment['status'] type already, and Tiger can put an employment
into this status any time its stage-2 AI check needs human review, so
the SDK should respect it regardless of whether a partner has opted
into that feature.

Addresses checklist item 1 in JOB_TITLE_ELIGIBILITY_CHECKLIST.md (on
feat/onboarding-job-title-eligibility-check).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore: ignore CLAUDE.local.md (#1400)

* chore: ignore CLAUDE.local.md

Adds an explicit .gitignore entry for CLAUDE.local.md, a local-only
companion to CLAUDE.md for per-developer notes that shouldn't be
checked in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: copy CLAUDE.local.md into new worktrees

CLAUDE.local.md is gitignored, so a plain `git worktree add` doesn't
carry it over and Claude Code loses that context in worktree sessions.
Copy it the same way example/.env is already seeded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(scripts): copy root .env.<env> files into new worktrees

npm run seed:onboarding -- --env=<name> reads .env.<name> from the repo
root, but create-worktree.ts only ever copied example/.env - a fresh
worktree silently lost access to gateway-targeting credentials like
.env.sandbox even though it's a root-level, not example/-level, file.

Mirrors the CLAUDE.local.md copy step just above it: glob root for any
.env.* file and copy each one into the new worktree. Also gitignores
.env.* (previously only .env itself was ignored), since these files
carry real credentials and weren't excluded from `git add -A`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore(deps-dev): update dependency dotenv to v18.0.1 (#1410)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps-dev): update dependency tsx to v4.23.15 (#1411)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant